package io.kiki.sba.registry.server.metadata.lease.filter;

import io.kiki.sba.registry.common.model.Node;
import io.kiki.sba.registry.common.model.Node.NodeType;
import io.kiki.sba.registry.common.model.Tuple;
import io.kiki.sba.registry.common.model.console.PersistenceData;
import io.kiki.sba.registry.common.model.console.PersistenceDataBuilder;
import io.kiki.sba.registry.common.model.constants.ValueConstants;
import io.kiki.sba.registry.common.model.metaserver.Lease;
import io.kiki.sba.registry.common.model.metaserver.NodeServerOperateInfo;
import io.kiki.sba.registry.common.model.metaserver.OperationInfo;
import io.kiki.sba.registry.common.model.metaserver.blacklist.RegistryForbiddenServerRequest;
import io.kiki.sba.registry.common.model.metaserver.nodes.CentralControlNode;
import io.kiki.sba.registry.common.model.metaserver.nodes.ClientInterfaceNode;
import io.kiki.sba.registry.common.model.metaserver.nodes.DataStoreNode;
import io.kiki.sba.registry.exception.SofaRegistryRuntimeException;
import io.kiki.sba.registry.server.metadata.provide.data.NodeOperatingService;
import io.kiki.sba.registry.server.metadata.provide.data.ProvideDataService;
import io.kiki.sba.registry.util.JsonUtils;
import lombok.Getter;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;


public class ForbiddenServerManagerImpl implements RegistryForbiddenServerManager {

    private static final Long NOT_FOUND_VERSION = 0L;
    protected final Logger logger = LoggerFactory.getLogger(ForbiddenServerManagerImpl.class);
    @Autowired
    private ProvideDataService provideDataService;
    @Autowired
    private NodeOperatingService nodeOperatingService;

    public ForbiddenServerManagerImpl() {
    }

    public ForbiddenServerManagerImpl(ProvideDataService provideDataService, NodeOperatingService nodeOperatingService) {
        this.provideDataService = provideDataService;
        this.nodeOperatingService = nodeOperatingService;
    }

    protected ForbiddenServer getForbiddenServers() {
        Tuple<Long, NodeServerOperateInfo> tuple = nodeOperatingService.queryOperateInfoAndVersion();

        if (tuple == null || tuple.o1 == null || tuple.o2 == null) {
            return new ForbiddenServer();
        }
        return new ForbiddenServer(tuple.o1, tuple.o2);
    }

    @Override
    public boolean addToBlacklist(RegistryForbiddenServerRequest request) {
        ForbiddenServer forbiddenServer = getForbiddenServers();
        if (forbiddenServer.add(request.getNodeType(), request.getCell(), request.getIp())) {
            return store(forbiddenServer);
        }
        return true;
    }

    @Override
    public boolean removeFromBlacklist(RegistryForbiddenServerRequest request) {
        ForbiddenServer forbiddenServer = getForbiddenServers();
        if (forbiddenServer.remove(request.getNodeType(), request.getCell(), request.getIp())) {
            return store(forbiddenServer);
        }
        return true;
    }

    @Override
    public boolean allowSelect(Lease<Node> lease) {
        ForbiddenServer forbiddenServer = getForbiddenServers();
        if (lease.getRenewal() instanceof ClientInterfaceNode) {
            return !forbiddenServer.contains(NodeType.client_interface, lease.getRenewal().getUrl().getIp());
        }
        if (lease.getRenewal() instanceof DataStoreNode) {
            return !forbiddenServer.contains(NodeType.data_store, lease.getRenewal().getUrl().getIp());
        }
        if (lease.getRenewal() instanceof CentralControlNode) {
            return !forbiddenServer.contains(NodeType.central_control, lease.getRenewal().getUrl().getIp());
        }
        return !forbiddenServer.contains(lease.getRenewal().getUrl().getIp());
    }

    protected boolean store(ForbiddenServer servers) {
        PersistenceData persistence = PersistenceDataBuilder.createPersistenceData(ValueConstants.NODE_SERVER_OPERATING_DATA_ID, JsonUtils.writeValueAsString(servers.nodeServerOperateInfo));

        return provideDataService.saveProvideData(persistence, servers.version);
    }

    protected class ForbiddenServer {
        @Getter
        final long version;
        final NodeServerOperateInfo nodeServerOperateInfo;

        public ForbiddenServer() {
            this(NOT_FOUND_VERSION, new NodeServerOperateInfo());
        }

        public ForbiddenServer(long version, NodeServerOperateInfo nodeServerOperateInfo) {
            this.version = version;
            this.nodeServerOperateInfo = nodeServerOperateInfo;
        }

        public boolean add(NodeType nodeType, String cell, String address) {
            switch (nodeType) {
            case central_control:
                return nodeServerOperateInfo.addMetas(cell, address);
            case data_store:
                return nodeServerOperateInfo.addDatas(cell, address);
            case client_interface:
                return nodeServerOperateInfo.addSessions(cell, address);
            default:
                throw new SofaRegistryRuntimeException("unexpected node type: " + nodeType);
            }
        }

        public boolean remove(NodeType nodeType, String cell, String address) {
            switch (nodeType) {
            case central_control:
                return nodeServerOperateInfo.removeMetas(cell, address);
            case data_store:
                return nodeServerOperateInfo.removeDatas(cell, address);
            case client_interface:
                return nodeServerOperateInfo.removeSessions(cell, address);
            default:
                throw new SofaRegistryRuntimeException("unexpected node type: " + nodeType);
            }
        }

        public boolean contains(String address) {

            return contains(NodeType.central_control, address) || contains(NodeType.data_store, address) || contains(NodeType.client_interface, address);
        }

        public boolean contains(NodeType nodeType, String address) {
            Set<OperationInfo> set;
            switch (nodeType) {
            case central_control:
                set = nodeServerOperateInfo.getMetas();
                break;
            case data_store:
                set = nodeServerOperateInfo.getDatas();
                break;
            case client_interface:
                set = nodeServerOperateInfo.sessionNodes();
                break;
            default:
                throw new SofaRegistryRuntimeException("unexpected node type: " + nodeType);
            }
            long count = set.stream().map(OperationInfo::getAddress).filter(operating -> StringUtils.equals(address, operating)).count();
            return count == 1;
        }


    }
}
